/*
* Copyright (C) Microsoft. All rights reserved.  
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not 
* use this file except in compliance with the License.  You may obtain a copy 
* of the License at http://www.apache.org/licenses/LICENSE-2.0  
* 
* THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED 
* WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, 
* MERCHANTABLITY OR NON-INFRINGEMENT. 
*
* See the Apache License, Version 2.0 for specific language governing 
* permissions and limitations under the License.
*
*/
#pragma once

#include <baja/common.hpp>
#include <baja/graphics.hpp>

#include <baja/graphics/scenes/scenes_fwd.hpp>
#include <baja/graphics/scenes/scenes_interfaces.hpp>
#include <baja/graphics/scenes/details/scene_node_base.hpp>

namespace baja { namespace graphics { namespace scenes {

namespace d = ::baja::graphics::scenes::details;

class scene_graph : public d::scene_node_base<iscene_graph>
{
public:
    scene_graph() {}

    static std::shared_ptr<scene_graph> create()
    {
        std::shared_ptr<scene_graph> returnValue = std::make_shared<scene_graph>();
        return returnValue;
    }

    // prepares a scene for rendering
    std::shared_ptr<iprepared_scene> prepareForRendering(const std::shared_ptr<iengine>& graphics)
    {
        return this->prepareScene(
            graphics, 
            false,
            prepared_scene_sort::priority 
            );
    }

    // prepares a scene for input processing
    std::shared_ptr<iprepared_scene> prepareForInput(const std::shared_ptr<iengine>& graphics)
    {
        return this->prepareScene(
            graphics, 
            true,
            prepared_scene_sort::frontToBack
            );
    }

    // prepares a scene.
    std::shared_ptr<iprepared_scene> prepareScene(
        const std::shared_ptr<iengine>& graphics,
        bool onlyAddNodesThatHandleInput,
        const prepared_scene_sort sort
        )
    {
        // NOTE: (from digit code) need to optimize so we only prepare when scene graph is dirty
        std::shared_ptr<prepared_scene> preparedSceneGraph;
        
        // create a scene transformer, as needed
        if (_sceneTransformer.get() == nullptr)
        {
            _sceneTransformer = createSceneTransformer();
        }

        // snapshot current scene transforms
        _sceneTransformer = graphics->snapshotCurrentSceneTransforms();

        // build up array of scene nodes to render
        preparedSceneGraph = prepared_scene::create(_sceneTransformer, sort);
        
        // visit all nodes and add them into the prepared scene
        this->visitChildNodes(shared_from_this(), preparedSceneGraph, onlyAddNodesThatHandleInput);

        return preparedSceneGraph;
    }

    // visits child nodes
    void visitChildNodes(
        const std::shared_ptr<iscene_node>& node,
        const std::shared_ptr<prepared_scene>& preparedScene,        
        bool onlyAddNodesThatHandleInput
        )
    {
        size_t childCount = 0;
        size_t i;

        if (node.get() != nullptr)
        {
            childCount = node->getChildCount();
            for (i = 0; i < childCount; i++)
            {
                std::shared_ptr<iscene_node> child;

                // get the child
                child = node->getChild(i);

                // determine if child is visible, if not skip it (and all of its children)
                bool isVisible = child->getIsVisible();
                if (!isVisible)
                {
                    continue;
                }

                // add to prepared scene
                if (onlyAddNodesThatHandleInput)
                {
                    BAJA_THROW(std::logic_error("NYI"));
                    /*
                    // NYI
                    // only add this node if it has an input events object
                    Auto<IUnknown> inputEvents;
                    child->GetInputEvents(&inputEvents);
                    if (inputEvents != NULL)
                    {
                        preparedScene->AddNode(child);
                    }
                    */
                }
                else
                {
                    // we're not filtering based on input, so always add the node
                    preparedScene->addNode(child);
                }

                // visit children
                this->visitChildNodes(child, preparedScene, onlyAddNodesThatHandleInput);
            }
        }
    }

    // nothing to render...
    void renderInternal(const std::shared_ptr<iengine>& graphics, const m::matrix44* localToWorld)
    {
    }

private:

    std::shared_ptr<iscene_transformer> _sceneTransformer;
};

inline std::shared_ptr<iscene_graph> createSceneGraph()
{
    std::shared_ptr<scene_graph> returnValue = scene_graph::create();
    return std::dynamic_pointer_cast<iscene_graph>(returnValue);
}

}}} // namespace baja::graphics::scenes
